package dk.viauc.cmclang.intlang;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.plaf.nimbus.State;

import dk.viauc.cmclang.intlang.ast.*;

public class ParserAST implements IParser {
	private IScanner scan;
	private Token currentTerminal;

	public ParserAST(IScanner scan) {
		this.scan = scan;
		currentTerminal = scan.scan();
	}

	public Object parseProgram() {

		Block block = parseBlock();

		if (currentTerminal.kind != Token.EOT)
			System.out.println("Tokens found after end of program");

		return new Program(block);
	}

	private Block parseBlock() {

		Block block = new Block();
		while (currentTerminal.kind != Token.EOT)
			block.functions.add(parseFunction());

		return block;
	}

	private FunctionDeclaration parseFunction() {
		accept(Token.FUNCTION);
		Identifier ident = parseIdentifier(); //
		accept(Token.KINDOF);
		Type type = parseType();

		// optional Part
		DeclarationList declarationList = null;
		if (currentTerminal.kind == Token.WITHPARAMS) {
			accept(Token.WITHPARAMS);
			declarationList = parseDeclarationList();
		}

		accept(Token.BEGIN);
		System.err.println("parseFuncDec " + currentTerminal.spelling);
		StatementList statementList = parseStatements();
		System.err.println("parseFuncDec AFTER STLIST "
				+ currentTerminal.spelling);
		accept(Token.ENDFUNCTION);

		return new FunctionDeclaration(ident, type, declarationList,
				statementList);
	}

	private Identifier parseIdentifier() {
		System.err.printf("ParserAST.parseIdentifier() %d,%s\n",
				currentTerminal.kind, currentTerminal.spelling);
		if (currentTerminal.kind == Token.IDENTIFIER) {
			Identifier res = new Identifier(currentTerminal.spelling);
			currentTerminal = scan.scan();

			return res;
		} else {
			System.out.println("Identifier expected");

			return new Identifier("???");
		}
	}

	private Type parseType() {
		System.err.println("ParserAST.parseType()");
		if (currentTerminal.kind == Token.TYPE) {
			Type res = new Type(currentTerminal.spelling);
			currentTerminal = scan.scan();

			return res;
		} else {
			System.out.println("Type expected");

			return new Type("???");
		}
	}

	private DeclarationList parseDeclarationList() {
		DeclarationList list = new DeclarationList();

		VariableDeclaration initDec = parseOneDeclaration();
		list.dec.add(initDec);

		while (currentTerminal.kind == Token.LET) {
			System.err.println("parseDeclaration LIST"
					+ currentTerminal.spelling);
			VariableDeclaration subseqDec = parseOneDeclaration();

			list.dec.add(subseqDec);
		}
		return list;
	}

	// ______________

	private VariableDeclaration parseOneDeclaration() {
		switch (currentTerminal.kind) {
		case Token.LET:
			accept(Token.LET);
			Identifier id = parseIdentifier();
			accept(Token.KINDOF);
			Type type = parseType();

			if (currentTerminal.kind == Token.ARRAYOF) {
				accept(Token.ARRAYOF);
				IntegerLiteral size = parseIntegerLiteral();
				ExpList list = new ExpList();
				if (Token.OPERATOR == currentTerminal.kind) {
					accept(Token.OPERATOR);

					while (currentTerminal.kind == Token.INTEGERLITERAL
							|| currentTerminal.kind == Token.BOOLITERAL
							|| currentTerminal.kind == Token.IDENTIFIER)
						list.exp.add(parseExpression());
				}
				return new VarArrayDeclaration(id, type, size, list);
			}

			if (currentTerminal.kind == Token.OPERATOR) {
				accept(Token.OPERATOR);
				Expression defExp = parseExpression();

				return new VariableDeclaration(id, type, defExp);
			}
			return new VariableDeclaration(id, type);

		default:
			System.out.println("LET expected");
			return null;
		}
	}

	private StatementList parseStatements() {
		StatementList stats = new StatementList();

		while (currentTerminal.kind == Token.IDENTIFIER
				|| currentTerminal.kind == Token.CALL
				|| currentTerminal.kind == Token.IF
				|| currentTerminal.kind == Token.ELIF
				|| currentTerminal.kind == Token.WHILE
				|| currentTerminal.kind == Token.READ
				|| currentTerminal.kind == Token.LET
				|| currentTerminal.kind == Token.WRITE
				|| currentTerminal.kind == Token.INTEGERLITERAL
				|| currentTerminal.kind == Token.BOOLITERAL
				|| currentTerminal.kind == Token.RETURN) {
			System.out.printf("ParserAST.parseStatements() %d,%s \n",
					currentTerminal.kind, currentTerminal.spelling);
			stats.statements.add(parseOneStatement());
		}

		return stats;
	}

	private Statement parseOneStatement() {
		switch (currentTerminal.kind) {
		case Token.IDENTIFIER:
		case Token.INTEGERLITERAL:
		case Token.BOOLITERAL:
		case Token.CALL:

			Expression exp = parseExpression();

			return new ExpressionStatement(exp);

		case Token.LET:
			DecsStatement declarations = new DecsStatement();
			declarations.stlist = parseDeclarationList();
			return declarations;

		case Token.RETURN:
			accept(Token.RETURN);
			return new ReturnStatement(parseExpression());
		case Token.ELIF:
			StatementList elifthenPart = null;
			Expression elififExp = null;
			if (currentTerminal.kind == Token.ELIF) {
				accept(Token.ELIF);
				elififExp = parseExpression();
				accept(Token.DO);
				elifthenPart = parseStatements();

			}
			return new IfStatement(elififExp, elifthenPart, null);

		case Token.IF:
			StatementList thenPart = null;
			Expression ifExp = null;
			if (currentTerminal.kind == Token.IF) {
				accept(Token.IF);
				ifExp = parseExpression();
				accept(Token.DO);
				thenPart = parseStatements();

			}

			StatementList elsePart = null;
			if (currentTerminal.kind == Token.ELSE) {

				accept(Token.ELSE);

				elsePart = parseStatements();
			}
			System.err.printf("ParserAST.parseENDIF %d,%s \n",
					currentTerminal.kind, currentTerminal.spelling);
			accept(Token.ENDIF);

			return new IfStatement(ifExp, thenPart, elsePart);

		case Token.WHILE:
			accept(Token.WHILE);
			Expression whileExp = parseExpression();
			accept(Token.DO);
			StatementList stats = parseStatements();
			accept(Token.ENDWHILE);

			return new WhileStatement(whileExp, stats);

		case Token.WRITE:
			accept(Token.WRITE);
			Expression sayExp = parseExpression();

			return new WriteStatament(sayExp);
		case Token.READ:
			accept(Token.READ);
			Expression readExp = parseExpression();// read the input from
													// keyboard
			return new ReadStatement(readExp);

		default:
			System.out.printf("Error in statement kind:  %d spelling: %s \n",
					currentTerminal.kind, currentTerminal.spelling);
			System.exit(0);
			return null;
		}
	}

	private Expression parseExpression() {
		Expression res = parsePrimary();
		while (currentTerminal.kind == Token.OPERATOR) {
			Operator op = parseOperator();
			Expression tmp = parsePrimary();

			res = new BinaryExpression(op, res, tmp);
		}

		return res;
	}

	private Expression parsePrimary() {
		System.err.printf("ParserAST.parsePrimary() %d,%s \n",
				currentTerminal.kind, currentTerminal.spelling);
		switch (currentTerminal.kind) {

		case Token.IDENTIFIER:
			Identifier name = parseIdentifier();
			if (currentTerminal.kind == Token.AT) {
				accept(Token.AT);
				IntegerLiteral lit = parseIntegerLiteral();
				return new ArrayGetExpression(name, lit);
			}
			return new VarExpression(name);

		case Token.INTEGERLITERAL:
			IntegerLiteral lit = parseIntegerLiteral();
			return new IntLitExpression(lit);

		case Token.BOOLITERAL:
			BoolLiteral blit = parseBoolLit();
			return new BoolLitExpression(blit);

		case Token.CALL:
			accept(Token.CALL);
			Identifier func = parseIdentifier();
			ExpList args = null;
			if (currentTerminal.kind == Token.INTEGERLITERAL
					|| currentTerminal.kind == Token.BOOLITERAL
					|| currentTerminal.kind == Token.IDENTIFIER)
				args = parseExpressionList();

			return new CallExpression(func, args);
		case Token.SIZEOF:
			accept(Token.SIZEOF);

			return parseExpression();
		default:
			System.out
					.printf("Error in primary, current kind: %d currentspelling: %s \n",
							currentTerminal.kind, currentTerminal.spelling);
			System.exit(0);
			return null;
		}
	}

	private ExpList parseExpressionList() {
		ExpList exps = new ExpList();

		exps.exp.add(parseExpression());
		while (currentTerminal.kind == Token.INTEGERLITERAL
				|| currentTerminal.kind == Token.BOOLITERAL
				|| currentTerminal.kind == Token.ARRAYOF
				|| currentTerminal.kind == Token.SIZEOF
				|| currentTerminal.kind == Token.CALL
				|| currentTerminal.kind == Token.IDENTIFIER) {

			exps.exp.add(parseExpression());
		}

		return exps;
	}

	/*
	 * private Identifier parseIdentifier() { if( currentTerminal.kind ==
	 * Token.IDENTIFIER ) { Identifier res = new Identifier(
	 * currentTerminal.spelling ); currentTerminal = scan.scan();
	 * 
	 * return res; } else { System.out.println( "Identifier expected" );
	 * 
	 * return new Identifier( "???" ); } }
	 */
	private BoolLiteral parseBoolLit() {
		if (currentTerminal.kind == Token.BOOLITERAL) {
			BoolLiteral res = new BoolLiteral(currentTerminal.spelling);
			currentTerminal = scan.scan();

			return res;
		} else {
			System.out.println("BOOLEAN literal expected");

			return new BoolLiteral("???");
		}
	}

	private IntegerLiteral parseIntegerLiteral() {
		if (currentTerminal.kind == Token.INTEGERLITERAL) {
			IntegerLiteral res = new IntegerLiteral(currentTerminal.spelling);
			currentTerminal = scan.scan();

			return res;
		} else {
			System.out.println("Integer literal expected");

			return new IntegerLiteral("???");
		}
	}

	private Operator parseOperator() {
		if (currentTerminal.kind == Token.OPERATOR) {
			Operator res = new Operator(currentTerminal.spelling);
			currentTerminal = scan.scan();

			return res;
		} else {
			System.out.println("Operator expected");

			return new Operator("???");
		}
	}

	private void accept(byte expected) {
		if (currentTerminal.kind == expected)
			currentTerminal = scan.scan();
		else {
			System.out.println("Expected token of kind " + expected
					+ " spelling is " + currentTerminal.spelling
					+ " token type " + currentTerminal.kind);
			System.exit(0);
		}
	}
}
